/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.vcs.cmdline.list;
import org.netbeans.modules.vcs.cmdline.VcsListRecursiveCommand;
import org.netbeans.modules.vcs.cmdline.Variables;
import org.netbeans.modules.vcs.VcsDirContainer;
import org.netbeans.modules.vcs.util.*;
import org.netbeans.modules.vcs.cmdline.exec.*;
import org.netbeans.modules.vcs.cmdline.commands.CvsModuleParser;
import java.io.*;
import java.util.*;
/**
*
* @author Martin Entlicher
* @version
*/
public class CvsListRecursiveCommand extends VcsListRecursiveCommand implements RegexListener {
private Debug E=new Debug("CvsListRecursiveCommand",true); // NOI18N
private Debug D=E;
private static final String[] examiningStrs = {"status: Examining", "server: Examining"}; // NOI18N
private String rootDir = null;
private String dir = null;
private String dirPath = null;
private String cmd = null;
private String cvsRoot = null;
private String cvsRepository = null;
private String relMount = null;
private boolean shouldFail = false;
private StringBuffer dataBuffer=new StringBuffer(4096);
//private StringBuffer errorBuffer = new StringBuffer(256);
private NoRegexListener stdoutNRListener = null;
private NoRegexListener stderrNRListener = null;
private RegexListener stderrListener = null;
private String dataRegex = null;
private String errorRegex = null;
private String input = null;
private long timeout = 0;
//private Hashtable modulesPaths = new Hashtable();
private CvsModuleParser moduleParser = new CvsModuleParser();
private StringBuffer moduleDefs = new StringBuffer();
/**
* @associates String
*/
private Vector examiningPaths = new Vector();
private String lastPathConverted = null;
private boolean lastPathFileDependent = false;
private String lastWorkingPath = null;
/*
private VcsDirContainer filesByNameCont = null;
private VcsDirContainer filesByNameContPath = null;
private Hashtable filesByName = null;
*/
/** Creates new CvsListRecursiveCommand */
public CvsListRecursiveCommand() {
}
private void initVars(Hashtable vars, String[] args) {
this.cmd = MiscStuff.array2string(args);
this.rootDir = (String) vars.get("ROOTDIR"); // NOI18N
if (this.rootDir == null) {
this.rootDir = "."; // NOI18N
//vars.put("ROOTDIR","."); // NOI18N
}
this.cvsRepository = (String) vars.get("CVS_REPOSITORY");
if (this.cvsRepository == null) {
this.cvsRepository = "";
}
cvsRepository = cvsRepository.replace('\\', '/');
this.dir = (String) vars.get("DIR"); // NOI18N
if (this.dir == null) {
this.dir = ""; // NOI18N
//vars.put("DIR","."); // NOI18N
}
this.dirPath = new String(dir.replace(java.io.File.separatorChar, '/')); // I have to be sure that I make new object
String module = (String) vars.get("MODULE"); // NOI18N
D.deb("rootDir = "+rootDir+", module = "+module+", dir = "+dir); // NOI18N
if (dir.equals("")) { // NOI18N
dir=rootDir;
if (module != null && module.length() > 0) dir += File.separator + module;
} else {
if (module == null)
dir=rootDir+File.separator+dir;
else
dir=rootDir+File.separator+module+File.separator+dir;
}
if (module.length() > 0) this.relMount = "/"+module.replace('\\', '/');
else relMount = "";
if (dir.charAt(dir.length() - 1) == File.separatorChar)
dir = dir.substring(0, dir.length() - 1);
D.deb("dir="+dir); // NOI18N
String dataRegex = (String) vars.get("DATAREGEX"); // NOI18N
if (dataRegex != null) this.dataRegex = dataRegex;
String errorRegex = (String) vars.get("ERRORREGEX"); // NOI18N
if (errorRegex != null) this.errorRegex = errorRegex;
D.deb("dataRegex = "+dataRegex+", errorRegex = "+errorRegex); // NOI18N
this.input = (String) vars.get("INPUT"); // NOI18N
if (this.input == null) this.input = "Cancel/n"; // NOI18N
this.timeout = ((Long) vars.get("TIMEOUT")).longValue(); // NOI18N
}
/*
private void addModulePath(String moduleDef) {
int index = moduleDef.indexOf(' ');
if (index < 0) return;
int len = moduleDef.length();
String moduleName = moduleDef.substring(0, index);
String moduleDir = moduleName;
while(true) {
while(index < len && Character.isWhitespace(moduleDef.charAt(index))) index++;
if (index >= len) return;
if (moduleDef.regionMatches(index, "-a", 0, "-a".length()) return;
if (moduleDef.regionMatches(index, "-d", 0, "-d".length()) {
index += "-d".length();
while(index < len && Character.isWhitespace(moduleDef.charAt(index))) index++;
if (index >= len) return;
int index2 = moduleDef.indexOf(' ', index);
if (index2 < 0) return;
moduleDir = moduleDef.substring(index, index2);
}
if (moduleDef.charAt(index) == '-') { // an other option
index += 2;
while(index < len && Character.isWhitespace(moduleDef.charAt(index))) index++;
if (index >= len) return;
int index2 = moduleDef.indexOf(' ', index);
if (index2 < 0) return;
index = index2;
} else break;
}
D.deb("Found module going into directory "+moduleDir);
}
*/
private void addModuleDefs() {
String defs = moduleDefs.toString();
int lastIndex = 0;
int beginIndex = 0;
int index = defs.indexOf('\n', lastIndex);
while (index >= 0) {
index++;
if (defs.length() <= index || !Character.isWhitespace(defs.charAt(index))) {
D.deb("addModule("+defs.substring(beginIndex, index).replace('\n', ' ')+")");
moduleParser.addModule(defs.substring(beginIndex, index).replace('\n', ' '));
beginIndex = index;
}
lastIndex = index;
index = defs.indexOf('\n', lastIndex);
}
moduleParser.resolveModuleLinks();
}
private void getModulesPaths(Hashtable vars) {
Variables v=new Variables();
String cmd = "${RUN} \\\"${CVS_EXE}\\\" checkout -c ${NUR}";
String prepared=v.expand(vars,cmd, true);
ExternalCommand ec=new ExternalCommand(prepared);
ec.setTimeout(timeout);
String mDataRegex = "^(.*)$";
try{
D.deb("stdout dataRegex = "+mDataRegex); // NOI18N
ec.addStdoutRegexListener(new RegexListener () {
public void match(String[] elements) {
//D.deb("getModulesPaths() match = "+elements[0]);
//moduleParser.addModule(elements[0]);
moduleDefs.append(elements[0]+"\n");
}
},mDataRegex);
}
catch (BadRegexException e){
if (stderrListener != null) {
String[] elements = { "CvsList: Bad data regex "+mDataRegex+"\n" }; // NOI18N
stderrListener.match(elements);
}
shouldFail=true ;
}
try{
ec.addStderrRegexListener(new RegexListener () {
public void match(String[] elements){
shouldFail=true ;
}
},errorRegex);
if (this.stderrListener != null) ec.addStderrRegexListener(stderrListener, errorRegex);
//ec.addStderrRegexListener(this,dataRegex); // Because of "Examining" status // NOI18N
}
catch (BadRegexException e){
if (stderrListener != null) {
String[] elements = { "CvsList: Bad error regex "+errorRegex+"\n" }; // NOI18N
stderrListener.match(elements);
}
shouldFail=true ;
}
if( ec.exec() != ExternalCommand.SUCCESS ){
shouldFail=true;
} else {
addModuleDefs();
}
}
//-----------------------------------
private void runStatusCommand(Hashtable vars){
Variables v=new Variables();
String prepared=v.expand(vars,cmd, true);
D.deb("prepared = "+prepared); // NOI18N
D.deb("DIR = '"+(String) vars.get("DIR")+"'"+", dir = '"+this.dir+"'"); // NOI18N
ExternalCommand ec=new ExternalCommand(prepared);
if (stderrListener != null) {
String[] command = { "LIST_SUB: "+prepared }; // NOI18N
stderrListener.match(command);
}
if (stderrNRListener != null) stderrNRListener.match("LIST_SUB: "+prepared); // NOI18N
ec.setTimeout(timeout);
ec.setInput(input);
try{
D.deb("stdout dataRegex = "+dataRegex); // NOI18N
ec.addStdoutRegexListener(this,dataRegex);
}
catch (BadRegexException e){
if (stderrListener != null) {
String[] elements = { "CvsList: Bad data regex "+dataRegex+"\n" }; // NOI18N
stderrListener.match(elements);
}
shouldFail=true ;
}
try{
ec.addStderrRegexListener(new RegexListener () {
public void match(String[] elements){
shouldFail=true ;
}
},errorRegex);
if (this.stderrListener != null) ec.addStderrRegexListener(stderrListener, errorRegex);
ec.addStderrRegexListener(new RegexListener () {
public void match(String[] elements) {
if (elements[0] == null || elements[0].length() == 0) return;
int index = -1;
for(int i = 0; i < examiningStrs.length; i++) {
D.deb("Comparing elements[0] = "+elements[0]+" to examining = "+examiningStrs[i]);
index = elements[0].indexOf(examiningStrs[i]);
if (index >= 0) {
index += examiningStrs[i].length();
break;
}
D.deb("Comp. unsuccessfull");
}
if (index >= 0) {
while (index < elements[0].length() && Character.isWhitespace(elements[0].charAt(index))) index++;
String path = elements[0].substring(index);
if (path.equals(".")) path = "";
D.deb("Got examining: "+path);
examiningPaths.add(path);
}
}
}, dataRegex);
//ec.addStderrRegexListener(this,dataRegex); // Because of "Examining" status // NOI18N
}
catch (BadRegexException e){
if (stderrListener != null) {
String[] elements = { "CvsList: Bad error regex "+errorRegex+"\n" }; // NOI18N
stderrListener.match(elements);
}
shouldFail=true ;
}
if (this.stdoutNRListener != null) ec.addStdoutNoRegexListener(stdoutNRListener);
if (this.stderrNRListener != null) ec.addStderrNoRegexListener(stderrNRListener);
if( ec.exec() != ExternalCommand.SUCCESS ){
shouldFail=true;
}
}
/**
* Get the path of file from the output information at given index.
* @param data the output data
* @param index the index to the file information
*/
private String getFilePath(String data, int index, String fileName) {
int begin = index;
while(Character.isWhitespace(data.charAt(begin))) begin++; // skip the space
while(!Character.isWhitespace(data.charAt(begin))) begin++; // skip the revision number
while(Character.isWhitespace(data.charAt(begin))) begin++; // skip the space
int end = data.indexOf('\n', begin);
D.deb("getFilePath(): end = "+end);
if (end < 0) return null;
String path = data.substring(begin, end);
D.deb("getFilePath(): path = "+path);
int nameIndex = path.lastIndexOf('/');
D.deb("nameIndex = "+nameIndex);
if (nameIndex < 0) return null;
if (nameIndex == 0) return (cvsRepository.length() > 0) ? null : "";
path = path.substring(0, nameIndex);
index = path.indexOf(cvsRepository/*+relMount*/);
D.deb(index+" = "+path+".indexOf("+cvsRepository+"+"+relMount+")");
if (index < 0) return null;
D.deb("getFilePath(): path = "+path+", index = "+index+", cvsRepository = "+cvsRepository);
if (path.length() <= cvsRepository.length()/* + relMount.length()*/)
return "";
else {
path = path.substring(index + cvsRepository.length() + 1);
String working = null;
if (!lastPathFileDependent && lastPathConverted != null && path.equals(lastPathConverted)) {
working = lastWorkingPath;
} else {
boolean[] fileDependent = new boolean[1];
fileDependent[0] = lastPathFileDependent;
String[] workings = moduleParser.convertRepPathToWorking(path, fileName, fileDependent);
if (workings != null) {
D.deb("getFilePath(): got workings = "+MiscStuff.arrayToString(workings));
String relMount0 = relMount+((dirPath.length() == 0) ? "" : "/"+dirPath);
if (relMount0.length() > 0) {
if (relMount0.charAt(0) == '/') relMount0 = relMount0.substring(1);
relMount0 += "/";
}
for(int i = 0; i < workings.length; i++) {
for(Enumeration enum = examiningPaths.elements(); enum.hasMoreElements(); ) {
String exPath = relMount0 + (String) enum.nextElement();
if (exPath.endsWith("/")) exPath = exPath.substring(0, exPath.length() - 1);
D.deb("Comparing working = "+workings[i]+" to exPath = "+exPath);
if (workings[i].equals(exPath)) {
working = workings[i];
break;
}
}
if (working != null) break;
}
}
lastPathFileDependent = fileDependent[0];
lastPathConverted = path;
lastWorkingPath = working;
//return working;
}
if (working == null) working = path;
D.deb("Have working = "+working+", return "+working.substring(relMount.length()));
return working.substring(relMount.length());
}
}
/**
* Get files and their statuses from the command output.
* @param filesByNameCont the container of files.
*/
private void fillHashtable(VcsDirContainer filesByNameCont) {
String data=new String(dataBuffer);
Hashtable filesByName = new Hashtable();
VcsDirContainer filesByNameContPath = filesByNameCont;
String last_filePath = filesByNameContPath.getPath();
int pos=0;
int index=0;
/* I expect file listing in the form: File: <filename> Status: <status>
* Followed by Repository Revision: <revision path>
* I suppose that revision path is the same as the working path.
* (Regex ^(File:.*Status:.*$)|(Repository Revision.*))
*/
filesByNameCont.setPath(dirPath);
filesByName = new Hashtable();
filesByNameCont.setElement(filesByName);
while(pos < data.length()) {
//int examIndex = getExaminingInfo(data, pos);
int fileIndex=data.indexOf("File:",pos); // NOI18N
int statusIndex=data.indexOf("Status:",pos); // NOI18N
if (fileIndex < 0 || statusIndex < 0) {
pos = data.length();
continue;
}
int nextIndex=data.indexOf("\n",statusIndex); // NOI18N
if (nextIndex < 0) {
nextIndex = data.length()-1;
}
//D.deb("fillHashtable: fileIndex = "+fileIndex+", statusIndex = "+statusIndex); // NOI18N
fileIndex+="File:".length(); // NOI18N
String fileName=data.substring(fileIndex,statusIndex).trim();
int i=-1;
if( (i=fileName.indexOf("no file")) >=0 ){ // NOI18N
fileName=fileName.substring(i+7).trim();
}
//D.deb("fileName="+fileName); // NOI18N
String fileDetails=data.substring(index,nextIndex);
//D.deb("fileDetails="+fileDetails); // NOI18N
int eolIndex=data.indexOf("\n",statusIndex); // NOI18N
String fileStatus="Unknown"; // NOI18N
if( statusIndex>=0 && eolIndex>=0 ){
statusIndex+="Status:".length(); // NOI18N
fileStatus=data.substring(statusIndex,eolIndex).trim();
}
//D.deb("fileStatus="+fileStatus); // NOI18N
int repositoryIndex = data.indexOf("Repository revision:", statusIndex);
if (repositoryIndex < 0) {
pos = data.length();
continue;
}
repositoryIndex += "Repository revision:".length();
String filePath = getFilePath(data, repositoryIndex, fileName);
D.deb("fillHashtable(): have filePath = "+filePath);
if (filePath != null && !filePath.equals(last_filePath)) {
VcsDirContainer parent = filesByNameCont.getParent(filePath);
if (parent != null) filesByNameContPath = parent.addSubdir(filePath);
else filesByNameContPath = filesByNameCont.addSubdirRecursive(filePath);
D.deb("parent = "+parent+((parent == null) ? "" : " path = "+parent.getPath()));
addDirName(filePath, filesByNameCont);
filesByName = (Hashtable) filesByNameContPath.getElement();
if (filesByName == null) {
filesByName = new Hashtable();
filesByNameContPath.setElement(filesByName);
}
D.deb("created new Container with path: "+filePath);
last_filePath = filePath;
}
//D.deb("fillHashTable: "+"fileName="+fileName+", fileStatus="+fileStatus); // NOI18N
String[] fileStatuses = new String[2];
fileStatuses[0] = fileName;
fileStatuses[1] = fileStatus;
filesByName.put(fileName,fileStatuses);
pos = repositoryIndex;
//fileIndex=data.indexOf("File:",pos); // NOI18N
//if (examIndex > 0 && examIndex < fileIndex) {
//examining = furtherExamining(data, examIndex += examiningStr.length());
//examIndex = data.indexOf(examiningStr, examIndex);
//}
}
}
/**
* Add the directory name to the proper container. Process the directory path recursively if necessary.
* @param filePath the directory full path
*/
private void addDirName(String filePath, VcsDirContainer filesByNameCont) {
if (filePath.length() == 0) return;
String[] fileStatuses = new String[2];
String dirName = MiscStuff.getFileNamePart(filePath) + "/";
String dirPath = MiscStuff.getDirNamePart(filePath);
D.deb("dirName = "+dirName+", dirPath = "+dirPath);
fileStatuses[0] = dirName;
fileStatuses[1] = "";
VcsDirContainer dirParent = filesByNameCont.getContainerWithPath(dirPath);
if (dirParent == null) {
//E.err("dirParent = null should NOT happen.");
// parent is somewhere out, don't care about this.
return;
} else {
Hashtable filesByName = (Hashtable) dirParent.getElement();
D.deb("Adding dir '"+dirName+"' to container with path "+dirParent.getPath());
if (filesByName == null) {
filesByName = new Hashtable();
dirParent.setElement(filesByName);
} else {
if (filesByName.get(dirName) != null) return; // the directory is already there
}
filesByName.put(dirName, fileStatuses);
if (dirParent == filesByNameCont) return;
addDirName(dirPath, filesByNameCont); // We have to ensure that all subdirectories are there
}
}
/**
* Add local directories with no status information.
* @param filesByName the files container
*/
private void addLocalFiles(String dir, VcsDirContainer filesByNameCont) {
File d = new File(dir);
String[] files = d.list();
Hashtable filesByName = (Hashtable) filesByNameCont.getElement();
if (filesByName == null) {
filesByName = new Hashtable();
filesByNameCont.setElement(filesByName);
}
if (files != null) {
String[] fileStatuses = new String[2];
fileStatuses[1] = ""; // NOI18N
for(int i=0;i<files.length;i++){
String fileName=files[i];
//D.deb("fileName="+fileName); // NOI18N
if( new File(d+File.separator+fileName).isDirectory() ){
fileName+="/"; // NOI18N
} else continue;
if( fileName.equals("CVS/") ){ // NOI18N
continue;
}
if( filesByName.get(fileName)==null ){
//D.deb("addLocalFiles: adding "+fileName+", to Container with path = "+filesByNameCont.getPath()); // NOI18N
fileStatuses[0] = fileName;
filesByName.put(fileName, fileStatuses.clone());
}
VcsDirContainer subdir = filesByNameCont.getDirContainer(files[i]);
if (subdir == null) subdir = filesByNameCont.addSubdir(filesByNameCont.getPath()+"/"+files[i]);
//D.deb("addLocalFiles: call add of "+dir+File.separator+files[i]+" to container "+subdir.getPath());
addLocalFiles(dir+File.separator+files[i], subdir);
}
}
}
/**
* List files of CVS Repository recursively.
* @param vars Variables used by the command
* @param args Command-line arguments
* @param filesByNameCont listing of files with statuses. For each directory there is a <code>Hashtable</code>
* with files.
* @param stdoutNRListener listener of the standard output of the command
* @param stderrNRListener listener of the error output of the command
* @param stdoutListener listener of the standard output of the command which
* satisfies regex <CODE>dataRegex</CODE>
* @param dataRegex the regular expression for parsing the standard output
* @param stderrListener listener of the error output of the command which
* satisfies regex <CODE>errorRegex</CODE>
* @param errorRegex the regular expression for parsing the error output
*/
public boolean listRecursively(Hashtable vars, String[] args, VcsDirContainer filesByNameCont,
NoRegexListener stdoutNRListener, NoRegexListener stderrNRListener,
RegexListener stdoutListener, String dataRegex,
RegexListener stderrListener, String errorRegex) {
this.stdoutNRListener = stdoutNRListener;
this.stderrNRListener = stderrNRListener;
this.stderrListener = stderrListener;
this.dataRegex = dataRegex;
this.errorRegex = errorRegex;
initVars(vars, args);
/*
this.filesByNameCont = filesByNameCont;
this.filesByNameContPath = filesByNameCont;
this.filesByName = new Hashtable();
*/
getModulesPaths(vars);
runStatusCommand(vars);
/*if (!shouldFail)*/ fillHashtable(filesByNameCont);
//addLocalFiles(dir, filesByNameCont);
return !shouldFail;
}
/**
* Matches the standard output of the command.
* @param elements a line of output
*/
public void match(String[] elements) {
dataBuffer.append(elements[0]+"\n"); // NOI18N
D.deb("match: append line '"+elements[0]+"'"); // NOI18N
}
}